#include <types.h>
#include <stdio.h>
#include <io.h>
#include "keyboard.h"

/* keyboard_send_command 函数  mod 值 */
#define CMD_ONLY 0		/* 无参命令 */
#define CMD_ARG  2		/* 有参命令 */
#define SEND_OK  3
#define RESEND   4		/* 重发命令 */


/* 键盘命令缓冲 status 值 */
#define CLR 0			/* 命令缓冲清空 */
#define CMD 1			/* 命令已发送 */
#define ARG 2		        /* 参数已发送 */


/* IO 0x60 端口只在　0x64 第１位为０的情况下可写 */
#define KBD_IO_WAIT   while(inportb(KBD_COMMAND)&0x02)

#define SCROLL_LED 0x01
#define NUM_LED    0x02
#define CAPS_LED   0x04
#define ALT        0x08
#define SHIFT      0x10
#define CONTROL    0x20

#define SET_CONTROL (status |= 0x20)
#define CLR_CONTROL (status &= 0xdf)
#define SET_SHIFT   (status |= 0x10)
#define CLR_SHIFT   (status &= 0xef)
#define SET_ALT     (status |= 0x08)
#define CLR_ALT     (status &= 0xf7)

/* LED 和 Control Shift Alt 状态 */
uint_8 status;
/*
  7 6 5 4 3 2 1 0
                ^--Scroll Lock
	      ^----Caps Lock
	    ^------Num Lock
	  ^--------Alt
	^----------Shift
      ^------------Control
    ^--------------0
  ^----------------0
*/

/* 要用什么办法才能将功能按键的事件放入队列？
   我决定对每一次键盘中断获取的按键使用两个字节存储。低字节用来存放 ASCII 值（如果是字符的话），
   高字节用来存放功能键编号和 Control Shift Alt 三个键的状态*/
static char key_map[0x100] = 
  {
    0, NUMLOCK, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t',
    'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', CAPSLOCK, 'a', 's',
    'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', LSHIFT, 0, 'z', 'x', 'c', 'v',
    'b', 'n', 'm', ',', '.', '/', RSHIFT, '+', LCTRL, ' ', [0x3a]=RCTRL, [0x45]='/', [0x46]='*', [0x54]='-',
    [0x71]=LALT, [0x72]=RALT, [0x7b]=INS, [0x79]=DEL, [0x7f]=HOM, [0x7a]=END, [0x6f]=PGUP, [0x7e]=PGDOWN,
    [0x56]=LEFT, [0x78]=UP, [0x55]=DOWN, [0x7d]=RIGHT, [0x47]=KPHOME, [0x4b]=KPLEFT, [0x4f]=KPEND, [0x48]=KPUP,
    [0x50]=KPDOWN, [0x52]=KPINS, [0x49]=KPPGUP, [0x4d]=KPRIGHT, [0x51]=KPPGDOWN, [0x53]=KPDEL, [0x64]=ESC,
    [0x58]=F1, [0x59]=F2, [0x5a]=F3, [0x5b]=F4, [0x5c]=F5, [0x5d]=F6, [0x5e]=F7, [0x5f]=F8, [0x60]=F9, [0x61]=F10,
    [0x62]=F11, [0x63]=F12, [0x57]=PSC, [0x74]='\\'
  };

static char shift_key_map[0x100] =
  {
    0, NUMLOCK, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', '\t',
    'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', CAPSLOCK, 'A', 'S',
    'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '\"', '~', LSHIFT, 0, 'Z', 'X', 'C', 'V',
    'B', 'N', 'M', '<', '>', '?', RSHIFT, '+', LCTRL, ' ', [0x3a]=RCTRL, [0x45]='/', [0x46]='*', [0x4e]='\n',
    [0x54]='-', [0x74]='|',
    
    [0x71]=LALT, [0x72]=RALT, [0x7b]=INS, [0x79]=DEL, [0x7f]=HOM, [0x7a]=END, [0x6f]=PGUP, [0x7e]=PGDOWN,
    [0x56]=LEFT, [0x78]=UP, [0x55]=DOWN, [0x7d]=RIGHT, [0x47]=KPHOME, [0x4b]=KPLEFT, [0x4f]=KPEND, [0x48]=KPUP,
    [0x50]=KPDOWN, [0x52]=KPINS, [0x49]=KPPGUP, [0x4d]=KPRIGHT, [0x51]=KPPGDOWN, [0x53]=KPDEL, [0x64]=ESC,
    [0x58]=F1, [0x59]=F2, [0x5a]=F3, [0x5b]=F4, [0x5c]=F5, [0x5d]=F6, [0x5e]=F7, [0x5f]=F8, [0x60]=F9, [0x61]=F10,
    [0x62]=F11, [0x63]=F12, [0x57]=PSC
  };


#define KP_BASE 0x47
static char num_key_map[0x15] =
  {
    '7', '8', '9', 0, '4','5','6','\n', '1', '2', '3', '0', '.'
  };

  





/* 字符中断缓冲区，用于判断多中断按键 */

#define KEYBUFFSIZE 0x100
static struct _keybuff
{
  uint_32 count;
  uint_8 key[KEYBUFFSIZE];
}keybuff;

static struct _kdb_cmd
{
  uint_8 command;
  uint_8 arg;
  uint_8 status;
  uint_8 mod;
}kbd_cmd;



/* 向键盘发送命令，当命令需要重新发送的时候，tc 记录下上次发送的命令，
 mod 为 0 时，发送新 command 命令， 为 1 时， 重发命令*/
static inline void keyboard_send_command(uint_8 command, uint_8 arg, uint_8 mod)
{
  switch(mod)
  {
  case CMD_ARG:
  case CMD_ONLY:
    while(kbd_cmd.status != CLR);	/* 如果命令缓冲非空，循环等待 */
    kbd_cmd.command = command;
    kbd_cmd.arg = arg;
    kbd_cmd.mod = mod;

    KBD_IO_WAIT;
    outportb(KBD_IN_BUFF, command);

    kbd_cmd.status = CMD;
    return;

  case SEND_OK:
    if ((kbd_cmd.status == CMD) && (kbd_cmd.mod == CMD_ARG)) /* 如果当前发送状态为命令已发送，且命令类型为带参命令，则继续发送参数 */
    {
      KBD_IO_WAIT;
      outportb(KBD_IN_BUFF, kbd_cmd.arg);
      kbd_cmd.status = ARG;
      return;
    }
    else			/* 如果发送状态为其他状态，或者命令类型为无参命令，更改状态为CLR， 结束*/
    {
      kbd_cmd.status = CLR;
      return;
    }

  case RESEND:
    switch(kbd_cmd.status)
    {
    case CMD:			/* 重发命令 */
      KBD_IO_WAIT;
      outportb(KBD_IN_BUFF, kbd_cmd.command);
      return;
      
    case ARG:			/* 重发参数 */
      KBD_IO_WAIT;
      outportb(KBD_IN_BUFF, kbd_cmd.arg);
      return;
    }
      
  }
}

  




void keyboard_change_status(uint_8 bit)
{
  /* 将给定参数位上的状态位反置 */
  if(status & bit)
    status &= ~bit;
  else
    status |= bit;

  /* 要修改的是 LED　位，改变 LED　的状态 */
  if(bit <= 0x04)
  {
#ifdef DEBUG1
    printf("Change LED Status\n");
#endif
    keyboard_send_command(0xED, status & 0x07, CMD_ARG);
  }
  
}






void keyboard_interrupt(uint_8 code)
{
  uint_16 key;

  /* Shift 是否按下？ */
  if(status & SHIFT)
    key = shift_key_map[code];
  else
    key = key_map[code];

  /* Caps Lock 是否置位？ */
  if(status & CAPS_LED)
  {
    if((key >= 65) && (key <= 90)) 
      key += 32;
    else
      if((key >= 97) && (key <=122))
	key -= 32;
  }

  /* Num Lock 是否置位 */
  if((code >= 0x47 && code <= 0x53) && (status & NUM_LED))
    key = num_key_map[code - KP_BASE];

  
  switch(code)
  {
    /* 键盘协议编码 */
  case 0x00:
    /*   case 0xaa: */
  case 0xee:
    /*   case 0xf1: */
  case 0xfa:
    keyboard_send_command(0, 0, SEND_OK);
    return;
  case 0xfc:
  case 0xfd:
    return;
      
  case 0xfe:
    keyboard_send_command(0, 0, RESEND);
    return;
      
  case 0xff:
#ifdef DEBUGg
    printf("Receive keyboard protocol scancode %x\n", code);
#endif
    return;
  case 0x01:
    keyboard_change_status(NUM_LED);
    break;
  case 0x1d:
    keyboard_change_status(CAPS_LED);
    break;
  case 0x76:
    keyboard_change_status(SCROLL_LED);
    break;

    /* 两个 control */
  case 0x38:
  case 0x7d:
    SET_CONTROL;
    break;
  case 0xb8:
  case 0xd7:
    CLR_CONTROL;
    break;
    
    /* 两个 Shift */
  case 0x2a:
  case 0x36:
    SET_SHIFT;
    break;
  case 0xaa:
  case 0xb6:
    CLR_SHIFT;
    break;

    /* 两个 Alt */
  case 0x71:
  case 0x72:
    SET_ALT;
    break;
  case 0xf1:
  case 0xf2:
    CLR_ALT;
    break;

  default:
    break;
  }


  /* 放入缓冲区 */
  if(key)
  {
    if(keybuff.count < KEYBUFFSIZE)
      keybuff.key[keybuff.count] = key;

#ifdef DEBUG
    putchar(key);
#endif
  }
  
}





void init_keyboard()
{

  printf("Init Keyboard\n");
  /* 所有按键 make code only */
  /*   keyboard_send_command(0xf9, NEW); */

  kbd_cmd.status = CLR;
  keybuff.count = 0;
  
    
  /* 发送命令，设置键盘扫描码字符集 */
  KBD_IO_WAIT;
  outportb(KBD_IN_BUFF, 0xf0);

  /* 设置字符集为 set 2 */
  KBD_IO_WAIT;
  outportb(KBD_IN_BUFF, 0x03);

  /* 设置 Num Locak 为打开状态， Caps Lock 和 Scroll Lock 为关闭状态 */
  status = 0x2;

  KBD_IO_WAIT;
  outportb(KBD_IN_BUFF, 0xed);
 
  KBD_IO_WAIT;
  outportb(KBD_IN_BUFF, status & 0x07);
}
